#-*- coding: utf-8 -*-

#_______________________ Simple As FucK Image Downloader _______________________
#
#                                by newfagpower

"""
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    © 2011 newfagpower

"""

import os
import sys
import logging
log = logging.getLogger(__name__)

import pygtk
pygtk.require("2.0")
import gtk
import gobject
gtk.gdk.threads_init()

import data
import message
import process
import downloader


class MainWindow:

    UPDATE_TIMEOUT_MILLI = 1500
    EXIT_TIMEOUT_MILLI = 1000
    
    def __init__ (self):
        """ load glade user interface, autoconnect events handlers
        then the widget 'exemplewidget' becomes self['exemplewidget']
        """
        # loading the user interface (UI)
        glade_filename = 'ui.glade'
        glade_dirname = os.path.dirname(__file__)
        complete_glade_filename = os.path.join(glade_dirname, glade_filename)
        self.builder = gtk.Builder()
        self.builder.add_from_file(complete_glade_filename)
        # autoconnect event handlers
        event_handlers = {}
        for (item_name, value) in self.__class__.__dict__.items():
            if callable(value) and item_name.startswith('on_'):
                event_handlers[item_name] = getattr(self,item_name) 
        self.builder.connect_signals(event_handlers)
        # HACK: manualy fix some UI issues, because glade sucks sometimes
        # expand the progressbar
        progress_cell = self['cellprogress']
        self['colprogress'].clear()
        self['colprogress'].pack_start(progress_cell, expand=True)
        self['colprogress'].set_attributes(progress_cell, value=2)
        self['colprogress'].set_sort_column_id(2)
        # set the right size for the treeview part 1/2 : hide the treeview
        self['tviewurl'].set_visible(False)
        # change the error message color
        self['evtboxerror'].modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color('#fd8'))
        # set the default download destination folder to the user home
        self['fchdestination'].set_current_folder(data.HOME_DIR)
        # enable multiple selection
        selection = self['tviewurl'].get_selection()
        selection.set_mode(gtk.SELECTION_MULTIPLE)
        # launch the scheduller
        self.scheduler = process.Scheduler()
        self.update_timeout = gobject.timeout_add(
                MainWindow.UPDATE_TIMEOUT_MILLI, self.update_callback)

    def __getitem__ (self, key):
        return self.builder.get_object(key)

    # event handlers
    # schema : on_<object>_<event> (self, source = None, event = None)
    
    # main window events
    
    def on_main_window_map(self, source=None, event=None):
        # more HACK: when the windows is shown
        # set the right size for the treeview part 2/2 : show the treeview
        self['tviewurl'].set_visible(True)
        
    def on_main_window_delete_event(self, source=None, event=None):
        self['lblerror'].set_text(message.Info.EXITING)
        self['hboxerror'].set_visible(True)
        # try stop all workers
        for info in self.scheduler.worker_dict.values():
            info['remove'] = True
        # stop updating the list view
        gobject.source_remove(self.update_timeout)
        # wait a bit and join all workers
        gobject.timeout_add(MainWindow.EXIT_TIMEOUT_MILLI,
                                self.cancel_all_callback)
        return True

    # toolbar events
    
    def on_toolnew_clicked(self, source=None, event=None):
        if self['hboxnewurl'].get_visible():
            self['hboxnewurl'].set_visible(False)
        else:
            self['entryurl'].set_text(message.Info.NEW_URL_DEFAULT_MSG)
            self['hboxnewurl'].set_visible(True)
        
    def on_toolcontinue_clicked(self, source=None, event=None):
        for url in self.get_selected_url_list():
            info = self.scheduler.worker_dict[url]
            info['pause'] = False

    def on_toolpause_clicked(self, source=None, event=None):
        for url in self.get_selected_url_list():
            info = self.scheduler.worker_dict[url]
            info['pause'] = True
    
    def on_toolchangedir_clicked(self, source=None, event=None):
        url_list = self.get_selected_url_list()
        dialog = gtk.FileChooserDialog(message.Info.CHOOSE_DIR,
                    self['main_window'],
                    gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                    (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                         gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dialog.set_default_response(gtk.RESPONSE_OK)
        if url_list:
            url = url_list[0]
            info = self.scheduler.worker_dict[url]
            dialog.set_current_folder(info['destination'])
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            new_dir = dialog.get_current_folder()
            for url in url_list:
                info = self.scheduler.worker_dict[url]
                info['destination'] = new_dir
        dialog.destroy()
    
    def on_toolzipit_toggled(self, source=None, event=None):
        active = self['toolzipit'].get_active()
        for url in self.get_selected_url_list():
            info = self.scheduler.worker_dict[url]
            info['zip_it'] = active

    def on_toolremove_clicked(self, source=None, event=None):
        url_list = self.get_selected_url_list()
        for url in url_list:
            info = self.scheduler.worker_dict[url]
            info['running'] = False
            info['remove'] = True
        # updating the toolbar to desactivate the remove button
        self.update_toolbar()
        gobject.timeout_add(MainWindow.EXIT_TIMEOUT_MILLI,
                                self.remove_callback, url_list)
            
    def on_toolstop_clicked(self, source=None, event=None):
        for url in self.get_selected_url_list():
            info = self.scheduler.worker_dict[url]
            info['running'] = False
    
    # new download form events
    
    def on_entryurl_activate(self, source=None, event=None):
        self.run_new_download()

    def on_btnurlok_activate(self, source=None, event=None):
        self.run_new_download()

    def on_btnerrorok_activate(self, source=None, event=None):
        self['lblerror'].set_text('')
        self['hboxerror'].set_visible(False)
    
    def on_entryurl_focus_in_event(self, source=None, event=None):
        if self['entryurl'].get_text() == message.Info.NEW_URL_DEFAULT_MSG:
            # FIXME: Change text style from italic-grey to normal-black
            self['entryurl'].set_text('')
    
    # downloads treeview events
    
    def on_tviewurl_cursor_changed(self, source=None, event=None):
        self.update_toolbar()
        
    # aboutbox events
    
    def on_toolabout_clicked(self, source=None, event=None):
        self['about'].show()
        
    def on_about_hide(self, source=None, event=None):
        self['about'].hide()
        return True

    # callbacks
    
    def cancel_all_callback(self):
        # FIXME: may wait foreveeeeeeer
        url_list = self.scheduler.worker_dict.keys()
        for url in url_list:
            self.scheduler.remove_worker(url)
        gtk.main_quit()
        
    def remove_callback(self, url_list):
        # removing each worker can take some time...
        for url in url_list:
            self.scheduler.remove_worker(url)
    
    def update_callback(self):
        remove_url_list = []
        for row in self['listurl']:
            url = row[0]
            info = None
            try:
                info = self.scheduler.worker_dict[url]
            except KeyError:
                # remove the associater worker
                remove_url_list.append(url)
                continue
            total = info['nb_done'] + info['nb_pending']
            # FIXME: find a better way to copy the name only once
            if info['name'] and row[1] != info['name']:
                row[1] = info['name']
            # set percentage
            if total != 0:
                row[2] = int(100.0 * info['nb_done'] / total)
            # set the infos
            row[3] = '{0} / {1}'.format(info['nb_done'], total)
            # set state
            if info['error']:
                if info['error'] == process.Worker.PREPARE_FAILED:
                    row[1] = message.Info.UNKNOWN
                    row[4] = message.Info.DOWNLOAD_PREPARE_FAILED
                elif info['error'] == process.Worker._SAVING_FAILED:
                    row[4] = message.Info.DOWNLOAD_SAVING_FAILED
            elif info['remove']: 
                row[4] = message.Info.DOWNLOAD_REMOVING
            elif not info['running']:
                row[4] = message.Info.DOWNLOAD_FINISHED
            elif info['pause']:
                row[4] = message.Info.DOWNLOAD_PAUSE
            elif info['nb_pending'] == 0:
                row[4] = message.Info.DOWNLOAD_WAITING
            else:
                row[4] = message.Info.DOWNLOAD_RUNNING
        # remove rows
        while remove_url_list:
            url = remove_url_list[0]
            path = 0
            for row in self['listurl']:
                if url == row[0]: break
                path += 1
            self['listurl'].remove(self['listurl'].get_iter(path))
            remove_url_list.remove(url)
        # update the toolbar sensitivity only when rows are selected
        if self.get_selected_url_list():
            self.update_toolbar()
        return True

    # actions
    
    def get_selected_url_list(self):
        (_, mixed_list) = self['tviewurl'].get_selection().get_selected_rows()
        row_index_list = [mixed[0] for mixed in mixed_list]
        row_list = [self['listurl'][row_index] for row_index in row_index_list]
        url_list = [row[0] for row in row_list]
        return url_list

    def update_toolbar(self):
        can_continue = False
        can_pause = False
        can_stop = False
        can_remove = False
        can_changezip = False
        can_changedir = False
        url_list = self.get_selected_url_list()
        # FIXME: that is not optimized at all
        once = True
        for url in url_list:
            info = None
            try:
                info = self.scheduler.worker_dict[url]
            except KeyError:
                # very rare case: the worker have been removed
                # but the interface have not been updated yet
                continue
            # set the zip tool state
            if once:
                self['toolzipit'].set_active(info['zip_it'])
                once = False
            if not info['remove']:
                can_remove = True
                if info['running']:
                    can_stop = True
                    can_cancel = True
                    can_changezip = True
                    can_changedir = True
                    if info['pause']:
                        can_continue = True
                    else:
                        can_pause = True
        self['toolcontinue'].set_sensitive(can_continue)
        self['toolpause'].set_sensitive(can_pause)
        self['toolstop'].set_sensitive(can_stop)
        self['toolremove'].set_sensitive(can_remove)
        self['toolzipit'].set_sensitive(can_changezip)
        self['toolchangedir'].set_sensitive(can_changedir)

    def run_new_download(self):
        try:
            url = self['entryurl'].get_text()
            dest = self['fchdestination'].get_current_folder()
            zip_it = self['cbtnzipimages'].get_active()
            self.scheduler.launch_worker(url, dest, zip_it)
            # add download to list
            self['listurl'].append((url, message.Info.SEARCHING, 0,
                            message.Info.SEARCHING, message.Info.SEARCHING))
            self['hboxnewurl'].set_visible(False)
            self['hboxerror'].set_visible(False)
        except process.WorkerAlreadyExists as e:
            # url is already being dowloaded
            log.warning('«{0}» already being downloaded: {1}'.format(url, e))
            self['lblerror'].set_text(message.Fail.URL_ALREADY_EXISTS)
            self['hboxerror'].set_visible(True)
        except downloader.NotFound as e:
            # malformed or unsupported url error
            log.warning('«{0}» malformed: {1}'.format(url, e))
            self['lblerror'].set_text(message.Fail.MALFORMED_URL)
            self['hboxerror'].set_visible(True)
        except Exception as e:
            # unknown error
            log.warning('«{0}» unknown error: {1}'.format(url, e))
            self['lblerror'].set_text('{0}: «{1}»'.format(
                                      message.Fail.UNKNOWN_ERROR, e))
            self['hboxerror'].set_visible(True)

    def run(self):
        self['main_window'].show()
        gtk.main()

